home *** CD-ROM | disk | FTP | other *** search
Wrap
// BEGIN FLOCK GPL // // Copyright Flock Inc. 2005-2007 // http://flock.com // // This file may be used under the terms of of the // GNU General Public License Version 2 or later (the "GPL"), // http://www.gnu.org/licenses/gpl.html // // Software distributed under the License is distributed on an "AS IS" basis, // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License // for the specific language governing rights and limitations under the // License. // // END FLOCK GPL // This is a library to access any bookmark service through the Delicious API. // Many services are providing a compatibility layer with Delicious, // so this library is used also for Magnolia and Shadows. var EXPORTED_SYMBOLS = ["DeliciousAPI"]; const CC = Components.classes; const CI = Components.interfaces; const CR = Components.results; const CU = Components.utils; CU.import("resource:///modules/ISO8601DateUtils.jsm"); /************************************************************************** * Module: Delicious API **************************************************************************/ // Constructor. function DeliciousAPI(aUrl, aLogger) { this._url = aUrl; this._logger = aLogger; } // Private data. DeliciousAPI.prototype._url = ""; DeliciousAPI.prototype._logger = null; /************************************************************************** * Delicious API Public Interface **************************************************************************/ /** * Call the specified del.ico.us API method. * @param AString aAPIMethod (in) * One of the API methods; "posts/add", etc. * @param object aArgs (in) * As documented at http://del.icio.us/help/api/ * @param object aAPIListener (in) * An object (deliciousAPIListener) with the following two methods: * void function onSuccess(in nsIDOMDocument aXML) * void function onError(in flockIError aError) * @param object aAuth (in) * An object with the following two properties: * AString user * AString password * @return nothing */ DeliciousAPI.prototype.call = function deliciousAPI_call(aAPIMethod, aArgs, aAPIListener, aAuth) { this._logger.debug("deliciousAPI_call('" + aAPIMethod + "', aArgs," + " aAPIListener, aAuth)"); // Convert args from object notation to an array of "key=value" pairs, // performing any escaping necessary for use in URL. var argsArray = []; for (var key in aArgs) { argsArray.push(encodeURIComponent(key) + "=" + encodeURIComponent(aArgs[key])); } // Build the complete API command: host, API method, and args. var url = this._url + aAPIMethod; if (argsArray.length) { url = url + "?" +argsArray.join("&"); } // Build an XMLHTTPRequest for sending the API call. var request = CC["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(CI.nsIXMLHttpRequest); var onReadyStateFunc = function deliciousAPI_call_onReadyStateFunc(eEvt) { if (request.readyState == 4) { if (request.status >= 200 && request.status < 300) { // Scrub input and create an nsIDOMDocument. var text = request.responseText.replace(/[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F]/g, ""); if (!text) { text = "<?xml version='1.0' standalone='yes'?>"; } var xml = CC["@mozilla.org/xmlextras/domparser;1"] .createInstance(CI.nsIDOMParser) .parseFromString(text, "text/xml"); aAPIListener.onSuccess(xml); } else { var error = CC["@flock.com/error;1"].createInstance(CI.flockIError); error.serviceErrorCode = request.status; error.serviceErrorString = request.responseText; switch (request.status) { case 401: // Bad login/password error.errorCode = CI.flockIError.FAVS_INVALID_AUTH; break; case 503: // Service unavailable error.errorCode = CI.flockIError.FAVS_UNAVAILABLE; break; default: // Unknown error code error.errorCode = CI.flockIError.FAVS_UNKNOWN_ERROR; break; } aAPIListener.onError(error); } } }; request.onreadystatechange = onReadyStateFunc; request.backgroundRequest = true; request.overrideMimeType("text/txt"); if (aAuth) { request.open("GET", url, true, aAuth.user, aAuth.password); } else { request.open("GET", url, true); } this._logger.debug("deliciousAPI_call request URL: '" + url + "'"); request.send(null); }; /** * Call the del.ico.us "posts/all" API method. * @param flockIListener aFlockListener (in) * @param aAuth (in) * An object with the following two properties: * AString user * AString password * @param AString aSeparator (in) * The delimiter to expect in the returned list of tags. * @return nothing * * Except when calling for the first time, "posts/update" should be called * before calling this, so that only new posts are returned. * * @see flockIListener */ DeliciousAPI.prototype.postsAll = function deliciousAPI_postsAll(aFlockListener, aAuth, aSeparator, aUpdateTime) { var tagSep = " "; if (aSeparator) { tagSep = aSeparator; } var api = this; var deliciousAPIListener = { onError: function deliciousAPI_postsAll_onError(aError) { if (aFlockListener) { aFlockListener.onError(null, "error", aError); } }, onSuccess: function deliciousAPI_postsAll_onSuccess(aXML) { // Validate the nsIDOMDocument response. if (!api.isExpectedResponse(aXML, "posts")) { api._logger.error("posts/all succeeded, bad xml response"); var error = CC["@flock.com/error;1"].createInstance(CI.flockIError); // FIXME: FAVS_, not OPML_. Clean up flockIError.idl. error.errorCode = CI.flockIError.OPML_INVALID_XML; if (aXML && aXML.documentElement) { api._logger.debug("tagName is: " + aXML.documentElement.tagName); error.serviceErrorString = aXML.documentElement.tagName; } else { api._logger.debug("aXML.documentElement is not defined"); error.serviceErrorString = "aXML.documentElement is not defined"; } aFlockListener.onError(null, "error", error); } else { var result = []; var children = aXML.documentElement.childNodes; for (var i=0; i<children.length; i++) { var child = children.item(i); if (child.nodeType != 1) { continue; } child.QueryInterface(CI.nsIDOMElement); if (child.tagName != "post") { continue; } if ((child.getAttribute("href") == "about:blank") || (child.getAttribute("href") == "")) { continue; } result.push({ URL: child.getAttribute("href"), name: child.getAttribute("description"), description: child.getAttribute("extended"), tags: child.getAttribute("tag"), tag: child.getAttribute("tag").split(tagSep), shared: child.getAttribute("shared"), datevalue: ISO8601DateUtils.parse(child.getAttribute("time")), hash: child.getAttribute("hash") }); } aFlockListener.onSuccess(result, aUpdateTime); } } }; this.call("posts/all", {}, deliciousAPIListener, aAuth); }; /** * Call the del.ico.us "posts/update" API method. * @param flockIListener aFlockListener (in) * @param object aAuth (in) * An object with the following two properties: * AString user * AString password * @param Date() aLastUpdate (in) * The last time we retrieved bookmarks from the server. * @param AString aSeparator (in) * The delimiter to expect in the returned list of tags. * @return nothing * * @see flockIListener */ DeliciousAPI.prototype.postsUpdate = function deliciousAPI_postsUpdate(aFlockListener, aAuth, aLastUpdate, aSeparator) { var tagSep = " "; if (aSeparator) { tagSep = aSeparator; } var api = this; var deliciousAPIListener = { onError: function deliciousAPI_postsUpdate_onError(aError) { if (aFlockListener) { aFlockListener.onError(null, "error", aError); } }, onSuccess: function deliciousAPI_postsUpdate_onSuccess(aXML) { // Validate the nsIDOMDocument response. if (!api.isExpectedResponse(aXML, "update")) { api._logger.error('posts/update succeeded, bad xml response'); var error = CC["@flock.com/error;1"].createInstance(CI.flockIError); // FIXME: FAVS_, not OPML_. Clean up flockIError.idl. error.errorCode = CI.flockIError.OPML_INVALID_XML; if (aXML && aXML.documentElement) { api._logger.debug("tagName is: " + aXML.documentElement.tagName); error.serviceErrorString = aXML.documentElement.tagName; } else { api._logger.debug("aXML.documentElement is not defined"); error.serviceErrorString = "aXML.documentElement is not defined"; } aFlockListener.onError(null, "error", error); } else { var lastUpdate = ISO8601DateUtils.parse(aXML.documentElement .getAttribute("time")); api._logger.debug("Last update on the server : " + lastUpdate); api._logger.debug("Last time updates retrieved: " + aLastUpdate); if (lastUpdate.getTime() > aLastUpdate.getTime()) { // There is new/updated stuff on the server if (api.timer) { api.timer.cancel(); } else { api.timer = CC["@mozilla.org/timer;1"] .createInstance(CI.nsITimer); } // Init a timer to start in 1.3 seconds. api.timer.initWithCallback({ notify: function deliciousAPI_postsUpdateTimer_notify(aTimer) { api.postsAll(aFlockListener, aAuth, tagSep, lastUpdate); } }, 1300, CI.nsITimer.TYPE_ONE_SHOT); } else { // No refresh needed if (aFlockListener) { aFlockListener.onSuccess(null, "nonew"); } } } } }; this.call("posts/update", {}, deliciousAPIListener, aAuth); }; /** * Call the del.ico.us "tags/get" API method. * @param flockIListener aFlockListener (in) * @param object aAuth (in) * An object with the following two properties: * AString user * AString password * @return nothing * * @see flockIListener */ DeliciousAPI.prototype.tagsGet = function deliciousAPI_tagsGet(aFlockListener, aAuth) { var api = this; var deliciousAPIListener = { onError: function deliciousAPI_tagsGet_onError(aError) { if (aFlockListener) { aFlockListener.onError(null, "error", aError); } }, onSuccess: function deliciousAPI_tagsGet_onSuccess(aXML) { // Validate the nsIDOMDocument response. if (!api.isExpectedResponse(aXML, "tags")) { api._logger.error('tags/get succeeded, bad xml response'); var error = CC["@flock.com/error;1"].createInstance(CI.flockIError); // FIXME: FAVS_, not OPML_. Clean up flockIError.idl. error.errorCode = CI.flockIError.OPML_INVALID_XML; if (aXML && aXML.documentElement) { api._logger.debug("tagName is: " + aXML.documentElement.tagName); error.serviceErrorString = aXML.documentElement.tagName; } else { api._logger.debug("aXML.documentElement is not defined"); error.serviceErrorString = "aXML.documentElement is not defined"; } aFlockListener.onError(null, "error", error); } else { var result = []; var children = aXML.documentElement.childNodes; for (var i=0; i<children.length; i++) { var child = children.item(i); if (child.nodeType != 1) { continue; } child.QueryInterface(CI.nsIDOMElement); if (child.tagName != "tag") { continue; } result.push({ tag: child.getAttribute("tag"), count: child.getAttribute("count") }); } aFlockListener.onSuccess(result, "success"); } } }; this.call("tags/get", {}, deliciousAPIListener, aAuth); }; /** * Helper function to test the API call response. * @param nsIDOMDocument aXML (in) * @param AString aCompareValue (in) * @return boolean * True - Response indicates successful API call * False - Response is missing, wrong, or reports an error */ DeliciousAPI.prototype.isExpectedResponse = function deliciousAPI_isExpectedResponse(aXML, aCompareValue) { // Validate the nsIDOMDocument response. if (!aXML || !aXML.documentElement || aXML.documentElement.tagName != aCompareValue) { return false; } return true; };